/** * */ package com.thinkbiganalytics.metadata.modeshape.support; /*- * #%L * thinkbig-metadata-modeshape * %% * Copyright (C) 2017 ThinkBig Analytics * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import com.thinkbiganalytics.classnameregistry.ClassNameChangeRegistry; import com.thinkbiganalytics.metadata.modeshape.JcrMetadataAccess; import com.thinkbiganalytics.metadata.modeshape.MetadataRepositoryException; import com.thinkbiganalytics.metadata.modeshape.common.JcrObject; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.modeshape.jcr.api.JcrTools; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeType; /** * Utility and convenience methods for accessing and manipulating nodes in the JCR API. Some * methods are duplicates of their JCR equivalents but do not throw the non-runtime RepositoryException. */ public class JcrUtil { /** * Creates a Path out of the arguments appropriate for JCR. * * @param first the first element * @param more any remaining elements * @return a path string */ public static Path path(String first, String... more) { return JcrPath.get(first, more); } /** * Creates a path out of the arguments appropriate for JCR. * * @param parent the parent node on whose path will be appended the additional elements * @param elements the remaining elements to form the path * @return a path string */ public static Path path(Node parent, String... elements) { try { return JcrPath.get(parent.getPath(), elements); } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to get the path of the node: " + parent, e); } } /** * Checks whether the given mixin node type is in effect for the given node. * * @param node the node * @param mixinType the mixin node type * @return <code>true</code> when the mixin node type is present, <code>false</code> instead. */ public static boolean hasMixinType(Node node, String mixinType) throws RepositoryException { for (NodeType nodeType : node.getMixinNodeTypes()) { if (nodeType.getName().equals(mixinType)) { return true; } } NodeType[] types = node.getPrimaryNodeType().getSupertypes(); if (types != null) { for (NodeType nt : types) { if (nt.getName().equals(mixinType)) { return true; } } } return false; } public static boolean isVersionable(JcrObject jcrObject) { return isVersionable(jcrObject.getNode()); } public static boolean isVersionable(Node node) { String name = ""; boolean versionable = false; try { name = node.getName(); versionable = hasMixinType(node, "mix:versionable"); return versionable; } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to check if versionable for Node " + name, e); } } public static String getName(Node node) { try { return node.getName(); } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to get name of Node " + node, e); } } public static boolean isNodeType(Node node, String typeName) { try { return node.getPrimaryNodeType().isNodeType(typeName); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the type of node: " + node, e); } } public static Node getParent(Node node) { try { return node.getParent(); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the parent of node: " + node, e); } } /** * Gets the nodes in a same-name-sibling node set with the given name and returns them as a list. */ public static List<Node> getNodeList(Node parent, String name) { return StreamSupport .stream(getIterableChildren(parent, name).spliterator(), false) .collect(Collectors.toList()); } public static Node getRootNode(Session session) { try { return session.getRootNode(); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the root node", e); } } public static boolean hasNode(Session session, String absPath) { try { if (absPath.startsWith("/")) { session.getNode(absPath); return true; } else { return session.getRootNode().hasNode(absPath); } } catch (PathNotFoundException e) { return false; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to check for the existence of the node at path " + absPath, e); } } public static boolean hasNode(Session session, String absParentPath, String name) { Node parentNode = getNode(session, absParentPath); return hasNode(parentNode, name); } public static boolean hasNode(Node parentNode, String name) { try { return parentNode.hasNode(name); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to check for the existence of the node named " + name, e); } } public static Node getNode(Session session, String absPath) { try { if (absPath.startsWith("/")) { return session.getNode(absPath); } else { return session.getRootNode().getNode(absPath); } } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node at the path: " + absPath, e); } } public static Node getNode(Node parentNode, String name) { try { return parentNode.getNode(name); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node named " + name, e); } } public static Node createNode(Node parentNode, String name, String nodeType) { try { return parentNode.addNode(name, nodeType); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to create the Node named " + name, e); } } public static void removeNode(Node node) { try { node.remove(); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to remove the node " + node, e); } } public static boolean removeNode(Node parentNode, String name) { try { if (parentNode.hasNode(name)) { parentNode.getNode(name).remove(); return true; } else { return false; } } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to remove the Node named " + name, e); } } public static List<Node> getNodesOfType(Node parentNode, String nodeType) { try { List<Node> list = new ArrayList<>(); NodeIterator itr = parentNode.getNodes(); while (itr.hasNext()) { Node node = (Node) itr.next(); if (node.isNodeType(nodeType)) { list.add(node); } } return list; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to create set of child nodes of type: " + nodeType, e); } } public static Iterable<Node> getIterableChildren(Node parent) { return getIterableChildren(parent, null); } public static Iterable<Node> getIterableChildren(Node parent, String name) { @SuppressWarnings("unchecked") Iterable<Node> itr = () -> { try { return name != null ? parent.getNodes(name) : parent.getNodes(); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the child nodes from: " + parent, e); } }; return itr; } public static <T extends JcrObject> List<T> getChildrenMatchingNodeType(Node parentNode, String childNodeType, Class<T> type) { try { String query = "SELECT child.* from [" + parentNode.getPrimaryNodeType() + "] as parent inner join [" + childNodeType + "] as child ON ISCHILDNODE(child,parent) WHERE parent.[mode:id] = '" + parentNode.getIdentifier() + "'"; return JcrQueryUtil.find(parentNode.getSession(), query, type); } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to find Children matching type " + childNodeType, e); } } public static <T extends JcrObject> T toJcrObject(Node node, String nodeType, Class<T> type) { return toJcrObject(node, nodeType, new DefaultObjectTypeResolver<T>(type)); } public static <T extends JcrObject> T toJcrObject(Node node, String nodeType, JcrObjectTypeResolver<T> typeResolver, Object... args) { try { if (nodeType == null || node.isNodeType(nodeType)) { T entity = constructNodeObject(node, typeResolver.resolve(node), args); return entity; } else { throw new MetadataRepositoryException("Unable to instanciate object of node type: " + nodeType); } } catch (RepositoryException e) { throw new MetadataRepositoryException("Unable to instanciate object from node: " + node, e); } } /** * get All Child nodes under a parentNode and create the wrapped JCRObject. */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, Class<T> type) { return getJcrObjects(parentNode, null, new DefaultObjectTypeResolver<T>(type)); } /** * get All Child nodes under a parentNode matching the name and type, and create the wrapped JCRObject the second argument, name, can be null to get all the nodes under the parent */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, String name, Class<T> type) { return getJcrObjects(parentNode, name, null, new DefaultObjectTypeResolver<T>(type)); } /** * get All Child nodes under a parentNode matching the type and create the wrapped JCRObject the second argument, name, can be null to get all the nodes under the parent */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, NodeType nodeType, Class<T> type) { return getJcrObjects(parentNode, nodeType, new DefaultObjectTypeResolver<T>(type)); } /** * get All Child nodes under a parentNode matching the name and type, returning a wrapped JCRObject the second argument, name, can be null to get all the nodes under the parent */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, String name, NodeType nodeType, Class<T> type) { return getJcrObjects(parentNode, name, nodeType, new DefaultObjectTypeResolver<T>(type)); } /** * get All Child nodes under a parentNode and create the wrapped JCRObject. */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, JcrObjectTypeResolver<T> typeResolver) { return getJcrObjects(parentNode, null, null, typeResolver); } /** * get All Child nodes under a parentNode of a certain type and create the wrapped JCRObject. */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, NodeType nodeType, JcrObjectTypeResolver<T> typeResolver) { return getJcrObjects(parentNode, null, nodeType, typeResolver); } /** * get All Child nodes under a parentNode and create the wrapped JCRObject the second argument, name, can be null to get all the nodes under the parent */ public static <T extends JcrObject> List<T> getJcrObjects(Node parentNode, String name, NodeType nodeType, JcrObjectTypeResolver<T> typeResolver, Object... args) { List<T> list = new ArrayList<>(); try { javax.jcr.NodeIterator nodeItr = null; if (StringUtils.isBlank(name)) { nodeItr = parentNode.getNodes(); } else { nodeItr = parentNode.getNodes(name); } if (nodeItr != null) { while (nodeItr.hasNext()) { Node n = nodeItr.nextNode(); if (nodeType == null || n.isNodeType(nodeType.getName())) { T entity = constructNodeObject(n, typeResolver.resolve(n), args); list.add(entity); } } } } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node named" + name, e); } return list; } /** * Get a creates a Wrapper object around the given node */ public static <T extends JcrObject> T getJcrObject(Node node, Class<T> type, Object... args) { return constructNodeObject(node, type, args); } /** * Get a child node relative to the parentNode and create the Wrapper object */ public static <T extends JcrObject> T getJcrObject(Node parentNode, String name, Class<T> type, Object... args) { try { Node n = parentNode.getNode(name); return getJcrObject(n, type, args); } catch (PathNotFoundException e) { return null; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node named" + name, e); } } /** * Get or Create a node relative to the Parent Node; checking out the parent node as necessary. */ public static Node getOrCreateNode(Node parentNode, String name, String nodeType, boolean forUpdate) { try { if (parentNode.hasNode(name)) { if (forUpdate) { JcrMetadataAccess.ensureCheckoutNode(parentNode); } return parentNode.getNode(name); } else { return addNode(parentNode, name, nodeType); } } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node named" + name, e); } } public static Node addNode(Node parentNode, String name, String nodeType) { try { JcrMetadataAccess.ensureCheckoutNode(parentNode); return parentNode.addNode(name, nodeType); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node named" + name, e); } } /** * Get or Create a node relative to the Parent Node; checking out the parent node as necessary. */ public static Node getOrCreateNode(Node parentNode, String name, String nodeType) { return getOrCreateNode(parentNode, name, nodeType, false); } /** * Get or Create a node relative to the Parent Node and return the Wrapper JcrObject */ public static <T extends JcrObject> T getOrCreateNode(Node parentNode, String name, String nodeType, Class<T> type) { return getOrCreateNode(parentNode, name, nodeType, type, null); } /** * Get or Create a node relative to the Parent Node and return the Wrapper JcrObject */ public static <T extends JcrObject> T getOrCreateNode(Node parentNode, String name, String nodeType, Class<T> type, Object... constructorArgs) { T entity = null; try { JcrTools tools = new JcrTools(); //if versionable checkout // if(isVersionable(parentNode)){ // JcrVersionUtil.checkout(parentNode); // } Node n = tools.findOrCreateChild(parentNode, name, nodeType); entity = createJcrObject(n, type, constructorArgs); //save ?? // JcrVersionUtil.checkinRecursively(n); // if(isVersionable(parentNode)){ // JcrVersionUtil.checkin(parentNode); // } } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve the Node named" + name, e); } return entity; } public static <T extends Serializable> T getGenericJson(Node parent, String nodeName) { return getGenericJson(parent, nodeName, false); } public static <T extends Serializable> T getGenericJson(Node parent, String nodeName, boolean allowClassNotFound) { try { Node jsonNode = parent.getNode(nodeName); return getGenericJson(jsonNode, allowClassNotFound); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to deserialize generic JSON node", e); } } public static <T extends Serializable> T getGenericJson(Node jsonNode) { return getGenericJson(jsonNode, false); } public static <T extends Serializable> T getGenericJson(Node jsonNode, boolean allowClassNotFound) { try { String className = jsonNode.getProperty("tba:type").getString(); @SuppressWarnings("unchecked") Class<T> type = (Class<T>) ClassNameChangeRegistry.findClass(className); return JcrPropertyUtil.getJsonObject(jsonNode, "tba:json", type); } catch (RepositoryException | ClassNotFoundException | ClassCastException e) { if (e instanceof ClassNotFoundException && allowClassNotFound) { //swallow this exception return null; } else { throw new MetadataRepositoryException("Failed to deserialize generic JSON property", e); } } } public static <T extends Serializable> void addGenericJson(Node parent, String nodeName, T object) { try { Node jsonNode = parent.addNode(nodeName, "tba:genericJson"); JcrPropertyUtil.setProperty(jsonNode, "tba:type", object.getClass().getName()); JcrPropertyUtil.setJsonObject(jsonNode, "tba:json", object); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to add a generic JSON node to the parent node: " + parent, e); } } /** * Create a new JcrObject (Wrapper Object) that invokes a constructor with at least parameter of type Node */ public static <T extends JcrObject> T addJcrObject(Node parent, String name, String nodeType, Class<T> type) { return addJcrObject(parent, name, nodeType, type, new Object[0]); } /** * Create a new JcrObject (Wrapper Object) that invokes a constructor with at least parameter of type Node */ public static <T extends JcrObject> T addJcrObject(Node parent, String name, String nodeType, Class<T> type, Object... constructorArgs) { try { Node child = parent.addNode(name, nodeType); return createJcrObject(child, type, constructorArgs); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to add new createJcrObject child node " + type, e); } } /** * Create a new JcrObject (Wrapper Object) that invokes a constructor with at least parameter of type Node */ public static <T extends JcrObject> T createJcrObject(Node node, Class<T> type) { return createJcrObject(node, type, new Object[0]); } public static Map<String, Object> jcrObjectAsMap(JcrObject obj) { String nodeName = obj.getNodeName(); String path = obj.getPath(); String identifier = null; try { identifier = obj.getObjectId(); } catch (RepositoryException e) { throw new RuntimeException(e); } String type = obj.getTypeName(); Map<String, Object> props = obj.getProperties(); Map<String, Object> finalProps = new HashMap<>(); if (props != null) { finalProps.putAll(finalProps); } finalProps.put("nodeName", nodeName); if (identifier != null) { finalProps.put("nodeIdentifier", identifier); } finalProps.put("nodePath", path); finalProps.put("nodeType", type); return finalProps; } public static Map<String, Object> nodeAsMap(Node obj) throws RepositoryException { String nodeName = obj.getName(); String path = obj.getPath(); String identifier = obj.getIdentifier(); String type = obj.getPrimaryNodeType() != null ? obj.getPrimaryNodeType().getName() : ""; Map<String, Object> props = JcrPropertyUtil.getProperties(obj); Map<String, Object> finalProps = new HashMap<>(); if (props != null) { finalProps.putAll(finalProps); } finalProps.put("nodeName", nodeName); if (identifier != null) { finalProps.put("nodeIdentifier", identifier); } finalProps.put("nodePath", path); finalProps.put("nodeType", type); return finalProps; } /** * Create a new JcrObject (Wrapper Object) that invokes a constructor with at least parameter of type Node */ public static <T extends JcrObject> T createJcrObject(Node node, Class<T> type, Object... constructorArgs) { T obj = constructNodeObject(node, type, constructorArgs); // TODO Removed since no nodes currently are versionable (Feed versioning removed) // if(JcrUtil.isVersionable(obj) && !node.isNew()){ // try { // String versionName = JcrVersionUtil.getBaseVersion(node).getName(); // obj.setVersionName(versionName); // obj.setVersionableIdentifier(JcrVersionUtil.getBaseVersion(node).getContainingHistory().getVersionableIdentifier()); // } catch (RepositoryException e) { // //this is fine... versionName is a nice to have on the object // } // } return obj; } /** * Create a new Node Wrapper Object that invokes a constructor with at least parameter of type Node */ public static <T extends Object> T constructNodeObject(Node node, Class<T> type, Object... constructorArgs) { T entity = null; try { if (constructorArgs != null) { constructorArgs = ArrayUtils.add(constructorArgs, 0, node); } else { constructorArgs = new Object[]{node}; } entity = ConstructorUtils.invokeConstructor(type, constructorArgs); } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { throw new MetadataRepositoryException("Failed to createJcrObject for node " + type, e); } return entity; } public static <T extends JcrObject> T getReferencedObject(Node node, String property, Class<T> type) { return getReferencedObject(node, property, new DefaultObjectTypeResolver<T>(type)); } public static <T extends JcrObject> T getReferencedObject(Node node, String property, JcrObjectTypeResolver<T> typeResolver) { try { Property prop = node.getProperty(property); return createJcrObject(prop.getNode(), typeResolver.resolve(prop.getNode())); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to dereference object of type using: " + typeResolver, e); } } /** * Creates an object set from the nodes of a same-name sibling property */ public static <T extends JcrObject> Set<T> getPropertyObjectSet(Node parentNode, String property, Class<T> objClass, Object... args) { try { Set<T> set = new HashSet<>(); NodeIterator itr = parentNode.getNodes(property); while (itr.hasNext()) { Node objNode = (Node) itr.next(); T obj = constructNodeObject(objNode, objClass, args); set.add(obj); } return set; } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to create set of child objects from property: " + property, e); } } public static NodeType getNodeType(Session session, String typeName) { try { return session.getWorkspace().getNodeTypeManager().getNodeType(typeName); } catch (NoSuchNodeTypeException e) { throw new MetadataRepositoryException("No node type exits named: " + typeName, e); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to retrieve node type named: " + typeName, e); } } public static Node copy(Session session, String srcPath, String destPath) { try { session.getWorkspace().copy(srcPath, destPath); return session.getNode(destPath); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to copy source path: " + srcPath + " to destination path: " + destPath, e); } } public static Node copy(Node srcNode, Node destNode) { try { Session sess = srcNode.getSession(); return copy(sess, srcNode.getPath(), destNode.getPath()); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to copy source node: " + srcNode + " to destination node: " + destNode, e); } } public static Node copy(Node srcNode, String destPath) { try { Session sess = srcNode.getSession(); return copy(sess, srcNode.getPath(), destPath); } catch (RepositoryException e) { throw new MetadataRepositoryException("Failed to copy source node: " + srcNode + " to destination path: " + destPath, e); } } private static class DefaultObjectTypeResolver<T extends JcrObject> implements JcrObjectTypeResolver<T> { private final Class<? extends T> type; public DefaultObjectTypeResolver(Class<? extends T> type) { super(); this.type = type; } @Override public Class<? extends T> resolve(Node node) { return this.type; } } }